#include "Applejack/applejackobject.hpp"
#include "Applejack/world.hpp"
#include <iostream>

using namespace Shiny;
using namespace Applejack;

ApplejackObject::ObjectLoaderMethod ApplejackObject::objectLoaderMethods[] = {
  { "type",          &ApplejackObject::LoadType           },
  { "shape",         &ApplejackObject::LoadShape          },
//{ "graphics",      &ApplejackObject::LoadGraphics       }, // Graphics must be loaded before all else
  { "fixedRotation", &ApplejackObject::LoadFixedRotation  },
  { "bullet",        &ApplejackObject::LoadBullet         },
  { "",              0 }
};

ApplejackObject::str2shape          ApplejackObject::shapeMap[] = {
  {"box",            &ApplejackObject::CreateBoxShape     },
  {"circle",         &ApplejackObject::CreateCircleShape  },
  {"polygon",        &ApplejackObject::CreatePolygonShape },
  {"",               0 }
};

ApplejackObject::ApplejackObject(World& world, Data data) : _world(world)
{
  Data::iterator current  = data.begin();
  Data::iterator end      = data.end();
  Data           graphics = data["graphics"];

  _ratio = world.Ratio();
  if (!(graphics.Nil()))
    LoadGraphics(graphics);
  for (; current != end ; ++current)
  {
    for (unsigned short i = 0 ; objectLoaderMethods[i].method != 0 ; ++i)
    {
      if ((*current).Key() == objectLoaderMethods[i].key)
      {
        (this->*objectLoaderMethods[i].method)(*current);
        break ;
      }
    }
  }
}

ApplejackObject::~ApplejackObject()
{
  std::list<b2FixtureDef>::iterator current = fixtures.begin();
  std::list<b2FixtureDef>::iterator end     = fixtures.end();

  for (; current != end ; ++current)
  {
    delete   (*current).shape;
    delete[] (char*)(*current).userData;
  }
}

void     ApplejackObject::LoadBullet(Data data)
{
  isBullet         = data.Value() == "1";
}

void     ApplejackObject::LoadFixedRotation(Data data)
{
  hasFixedRotation = data.Value() == "1";
}

void     ApplejackObject::LoadType(Data data)
{
  if      (data.Value() == "dynamic")
    type = b2_dynamicBody;
  else if (data.Value() == "static")
    type = b2_staticBody;
  else if (data.Value() == "kinematic")
    type = b2_kinematicBody;
}

void     ApplejackObject::LoadShape(Data data)
{
  Data         fieldName      = data["name"];
  Data         fieldShapeType = data["type"];
  Data         fieldDensity   = data["density"];
  Data         fieldFriction  = data["friction"];
  Data         fieldCollision = data["fieldCollision"];
  Data         fieldSensor    = data["sensor"];
  b2FixtureDef fixture;

  if (!(fieldShapeType.Nil()))
  {
    for (unsigned char i = 0 ; shapeMap[i].method != 0 ; ++i)
    {
      if (fieldShapeType.Value() == shapeMap[i].key)
      {
        fixture.shape  = (this->*shapeMap[i].method)(data, _ratio);
        break ;
      }
    }
    if (!(fieldDensity.Nil()))
      fixture.density             = fieldDensity;
    if (!(fieldFriction.Nil()))
      fixture.friction            = fieldFriction;
    if (!(fieldSensor.Nil()))
      fixture.isSensor            = fieldSensor.Value() == "1";
    if (!(fieldCollision.Nil()))
    {
      fixture.filter.maskBits     = FixtureLoadCollisionMask(fieldCollision);
      fixture.filter.categoryBits = 1;
    }
    if (!(fieldName.Nil()))
    {
      char* name = new char[fieldName.Value().length() + 1];

      fieldName.Value().copy(name, fieldName.Value().length());
      name[fieldName.Value().length()] = 0;
      fixture.userData = (void*)name;
    }
    fixtures.push_back(fixture);
  }
  if (fixture.shape == 0)
    std::cerr << "Unable to load body shape" << std::endl;
}

void     ApplejackObject::LoadGraphics(Data data)
{
  Data::iterator  begin = data.begin();
  Data::iterator  end   = data.end();

  while (begin != end)
  {
    SpriteAnimate::Animation    anim;
    const std::string&          type  = (*begin).Key();
    const std::string&          value = (*begin).Value();

    if (type == "texture")
    {
      const sf::Texture* texture = (_world.GetTextures())[value];

      anim = SpriteAnimate::Animation(texture);
    }
    else if (type == "animation")
    {
      const AnimationList::Animation* anim_ = (_world.GetAnimations())[value];

      if (anim_)
        anim = SpriteAnimate::Animation(*anim_);
      else
        ErrorManager::Get()->AddError("Can't load animation " + value, ErrorManager::Warning);
    }
    graphics.push_back(anim);
    ++begin;
  }
  if (graphics.size())
  {
    const SpriteAnimate::Animation& anim = graphics.front();

    halfSize.x = anim.Rect().Width  / 2;
    halfSize.y = anim.Rect().Height / 2;
  }
}

b2Shape* ApplejackObject::CreateBoxShape(Data fieldShape, unsigned int ratio)
{
  b2PolygonShape* shape      = new b2PolygonShape;
  Data            size       = fieldShape["size"];
  Data            fieldAngle = fieldShape["angle"];
  Data            fieldX     = fieldShape["size"]["x"];
  Data            fieldY     = fieldShape["size"]["y"];
  Data            posX       = fieldShape["position"]["x"];
  Data            posY       = fieldShape["position"]["y"];
  b2Vec2          position;
  float           angle         = 0;

  if (!(fieldAngle.Nil()))
    angle = (float)(fieldAngle) * (b2_pi / 180.f);
  if (!(fieldX.Nil()) && !(fieldY.Nil()))
  {
    float         sizeX  = fieldX;
    float         sizeY  = fieldY;

    if (!(posX.Nil()) && !(posY.Nil()))
    {
      float symetryPosX = (halfSize.x * 2) - sizeX - (float)(posX);
      float symetryPosY = (halfSize.y * 2) - sizeY - (float)(posY);
          
      position.x = (halfSize.x - ((sizeX / 2) + symetryPosX)) / ratio;
      position.y = (halfSize.y - ((sizeY / 2) + symetryPosY)) / ratio;
    }
    shape->SetAsBox((sizeX / ratio) / 2, ((sizeY / ratio) / 2), position, angle);
  }
  return (shape);
}

b2Shape* ApplejackObject::CreateCircleShape(Data fieldShape, unsigned int ratio)
{
  b2CircleShape* shape    = new b2CircleShape;
  Data           radius   = fieldShape["radius"];
  Data           x        = fieldShape["position"]["x"];
  Data           y        = fieldShape["position"]["y"];

  if (!(radius.Nil()))
  {
    shape->m_radius = (float)(radius) / ratio;

    if (!(x.Nil()) && !(y.Nil()))
    {
      float valueRadius = shape->m_radius * 2;
      float symetryPosX = (halfSize.x * 2) - (valueRadius) - (float)(x);
      float symetryPosY = (halfSize.y * 2) - (valueRadius) - (float)(y);

      shape->m_p.x = (halfSize.x - (shape->m_radius + symetryPosX)) / ratio;
      shape->m_p.y = (halfSize.y - (shape->m_radius + symetryPosY)) / ratio;
    }
  }
  return (shape);
}

b2Shape* ApplejackObject::CreatePolygonShape(Data fieldShape, unsigned int ratio)
{
  b2PolygonShape*     shape    = 0;
  Data                vertices = fieldShape["vertices"];
  std::vector<b2Vec2> arrayVertices;
  b2Vec2*             paramVertices;

  if (!(vertices.Nil()))
  {
    Data::iterator    current = vertices.begin();
    Data::iterator    end     = vertices.end();

    shape = new b2PolygonShape;
    while (current != end)
    {
      Data        x = (*current)["x"];
      Data        y = (*current)["y"];
      b2Vec2      pos;

      if (x.Nil() || y.Nil())
        return (0);
      pos.Set(((float)(x) - halfSize.x) / ratio, ((float)(y) - halfSize.y) / ratio);
      arrayVertices.push_back(pos);
      ++current;
    }
    paramVertices = new b2Vec2[arrayVertices.size()];
    for (unsigned int i = 0 ; i < arrayVertices.size() ; ++i)
      paramVertices[i] = arrayVertices[i];
    shape->Set(paramVertices, arrayVertices.size());
  }
  return (shape);
}

char     ApplejackObject::FixtureLoadCollisionMask(Data data)
{
  std::string strMask = data.Value();
  char        i, collisionMask;

  collisionMask = 0;
  for (i = 0 ; i < strMask.length() ; ++i)
  {
    if (strMask[i] == '1')
      collisionMask + 1;
    collisionMask << 1;
  }
  if (i < 8)
    collisionMask << (8 - i);
  return (collisionMask);
}

/*
 * ObjectList
 */
void ObjectList::Load(World& world, Data data)
{
  Data::iterator current = data.begin();
  Data::iterator end     = data.end();

  for (; current != end ; ++current)
  {
    ApplejackObject* object = new ApplejackObject(world, (*current));

    Add((*current).Key(), object);
  }
}

/*
 * Joint Factory
 */
